﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.Win32.SafeHandles;
using NHook.Native;

namespace NHook
{
	public class RemoteUnmanagedMemoryStream : Stream
	{

		public override bool CanRead
		{
			get
			{
				return true;
			}
		}

		public override bool CanSeek
		{
			get
			{
				return false;
			}
		}

		public override bool CanWrite
		{
			get
			{
				return true;
			}
		}

		SafeFileHandle _ProcessHandle;
		uint _UnflushedCount;

		IntPtr _StartUnflushedAddr;
		IntPtr _CurrentAddr;


		public RemoteUnmanagedMemoryStream(SafeFileHandle processHandle, IntPtr addr)
		{
			_ProcessHandle = processHandle;
			_CurrentAddr = addr;
			_StartUnflushedAddr = addr;
		}

		public override void Flush()
		{
			if(_UnflushedCount == 0)
				return;
			Patch.FlushInstructionCache(_ProcessHandle.DangerousGetHandle(),
				_StartUnflushedAddr,
				new UIntPtr(_UnflushedCount));
			_UnflushedCount = 0;
			_StartUnflushedAddr = _CurrentAddr;
		}

		public override long Length
		{
			get
			{
				throw new NotSupportedException();
			}
		}

		public override long Position
		{
			get
			{
				return _CurrentAddr.ToInt64();
			}
			set
			{
				Flush();
				_CurrentAddr = new IntPtr(value);
			}
		}

		public override int Read(byte[] buffer, int offset, int count)
		{
			Flush();
			using(new PageProtect(_ProcessHandle, _CurrentAddr, PageProtection.ExecuteReadWrite, (uint)count))
			{
				var result = MemoryUtil.ReadMemory(
					_ProcessHandle.DangerousGetHandle(),
					_CurrentAddr,
					count);
				Array.Copy(result, 0, buffer, offset, count);
				_CurrentAddr += count;
			}
			return count;
		}
		public override void Write(byte[] buffer, int offset, int count)
		{
			using(new PageProtect(_ProcessHandle, _CurrentAddr, PageProtection.ExecuteReadWrite, (uint)count))
			{
				byte[] input = new byte[count];
				Array.Copy(buffer,offset,input,0,count);
				MemoryUtil.WriteMemory(
					_ProcessHandle.DangerousGetHandle(),
					_CurrentAddr,
					input);
				_CurrentAddr += count;
			}
			Flush();
		}

		public override long Seek(long offset, SeekOrigin origin)
		{
			throw new NotSupportedException();
		}

		public override void SetLength(long value)
		{
			throw new NotSupportedException();
		}
	}
}
